package br.com.honorato.dao;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.NonUniqueResultException;
import javax.persistence.PersistenceException;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import br.com.honorato.enun.EDaoErro;
import br.com.honorato.exception.DAOException;

public class JpaDAO<T> implements GenericDAO<T>, Serializable
{
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	protected Class<T> persistentClass;
	private EntityManager manager;
	private ArrayList<Predicate> predicates;
	private CriteriaQuery<T> criteriaQuery;
	private Root<T> fromRoot;

	@SuppressWarnings("unchecked")
	public JpaDAO(EntityManager manager)
	{
		Type superClasse = getClass().getGenericSuperclass();
		Type tipo = ((ParameterizedType) superClasse).getActualTypeArguments()[0];
		this.persistentClass = (Class<T>) tipo;
		this.manager = manager;
		initClass((Class<T>) tipo);
	}

	private void initClass(Class<T> classe)
	{
		setCriteriaQuery(getCriteriaBuilder().createQuery(classe));
		setFromRoot((getCriteriaQuery().from(classe)));
		getCriteriaQuery().select(getFromRoot());
	}

	@Override
	public EntityManager getEntityManager()
	{
		return this.manager;
	}

	@Override
	public T selectByKey(Object id)
	{
		return (T) this.getEntityManager().find(this.persistentClass, id);
	}

	@Override
	public List<T> selectAll()
	{
		List<T> result = getTypedQuery().getResultList();
		return result;
	}

	@Override
	public T save(T object) throws DAOException
	{
		try
		{
			object = this.getEntityManager().merge(object);
			return object;
		}
		catch (PersistenceException ex)
		{
			throw new DAOException(EDaoErro.PersistenceException.getCode(), EDaoErro.PersistenceException.getLabel(new String[]
			{ object.getClass().getCanonicalName() }));
		}
	}

	@Override
	public void delete(T object) throws DAOException
	{
		try
		{
			this.getEntityManager().remove(object);
		}
		catch (Exception ex)
		{
			// TODO recuperar exception correta
			throw new DAOException(EDaoErro.PersistenceException.getCode(), EDaoErro.PersistenceException.getLabel(new String[]
			{ object.getClass().getCanonicalName() }));
		}
	}

	public CriteriaBuilder getCriteriaBuilder()
	{
		return this.getEntityManager().getCriteriaBuilder();
	}

	public ArrayList<Predicate> getPredicates()
	{
		if (predicates == null)
			predicates = new ArrayList<Predicate>();
		return predicates;
	}

	public void setPredicates(ArrayList<Predicate> predicates)
	{
		this.predicates = predicates;
	}

	public CriteriaQuery<T> getCriteriaQuery()
	{
		return criteriaQuery;
	}

	public void setCriteriaQuery(CriteriaQuery<T> query)
	{
		this.criteriaQuery = query;
	}

	public Root<T> getFromRoot()
	{
		return fromRoot;
	}

	public void setFromRoot(Root<T> fromRoot)
	{
		this.fromRoot = fromRoot;
	}

	public TypedQuery<T> getTypedQuery()
	{
		// TypedQuery<T> typedQuery =
		// this.getEntityManager().createQuery(getCriteriaQuery());
		//
		// for (ParameterToSet param: getListValueParameter()) {
		// typedQuery.setParameter(param.getParamName(), param.getParamValue());
		// }

		return this.getEntityManager().createQuery(getCriteriaQuery());
	}

	public void setWhereInQueryWhithPredicates()
	{
		if (getPredicates().size() > 0)
		{
			Predicate[] params = new Predicate[getPredicates().size()];
			getCriteriaQuery().where(getPredicates().toArray(params));
			getPredicates().clear();
		}
	}

	protected List<T> recoveryListByPredicate(Class<T> classe) throws DAOException
	{
		setWhereInQueryWhithPredicates();
		return getTypedQuery().getResultList();
	}

	protected T recoverySingleByPredicate(Class<T> classe) throws DAOException
	{
		try
		{
			setWhereInQueryWhithPredicates();
			return (T) getTypedQuery().getSingleResult();
		}
		catch (NoResultException err)
		{
			return null;
		}
		catch (NonUniqueResultException err)
		{
			throw new DAOException(EDaoErro.NonUniqueResultException.getCode(), EDaoErro.NonUniqueResultException.getLabel(new String[]
			{ classe.getCanonicalName() }));
		}
	}

}